package on;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;


@Entity
/**
 * Clase que representa los canales opticos que forman parte de la red.
 * <p>
 * Descripcion: Canal optico utilizado para representar la agrupacion de enlaces
 * que estan definidos por longitud de onda, por fibra, y por canal.
 * </p>
 * 
 * @author aamadeo
 * 
 */
public class Link implements Comparable<Link> {

	public static String BUFFER_DEBUG = null;

	@OneToMany(cascade=CascadeType.ALL)
	@OrderBy("wavelength ASC")
	private Set<LightLink> enlaces = new HashSet<LightLink>();
	
	@Transient
	private Set<LightLink> enlacesNecesarios = new HashSet<LightLink>();
			
	@Id 
	@GeneratedValue 
	private int id;
	
	@Transient
	private int fibersExtra = 0;
	@Transient
	private int bandsExtra = 0;
	@Transient
	private int wavelengthsExtra = 0;
	@Transient
	private int freeBandwidth = 0;
	
	private int cost;
	
	@ManyToOne(cascade=CascadeType.ALL)
	private Nodo source;
	
	@ManyToOne(cascade=CascadeType.ALL)
	private Nodo destination;
	
	@ManyToOne(cascade=CascadeType.ALL)
	private Red red;
	
	@Transient
	private boolean blocked = false;
	
	public Link(){}
	
	/**
	 * Constructor principal. Setea los atributos principales y genera los
	 * enlaces del canal.
	 * 
	 * @param origen
	 *            Nodo Origen
	 * @param destino
	 *            Nodo Destino
	 * @param fibras
	 *            Cantidad de fibras en el canal
	 * @param ldos
	 *            Cantidad de Longitudes de Onda por fibra
	 */
	public Link(Nodo a, Nodo b, Red red) {
		this.source = a;
		this.destination = b;
		this.fibersExtra = 0;
		this.bandsExtra = 0;
		this.wavelengthsExtra = 0;
		this.red = red;
		this.freeBandwidth = red.getBandWidth();

		this.enlaces.clear();
		crearEnlaces(red);
		this.enlacesNecesarios.addAll(this.enlaces);
	}
	
	public void crearEnlaces(Red red){
		for (int f = 0; f < red.getF(); f++) {
			for (int b = 0; b < red.getB(); b++) {
				for(int w = 0; w < red.getW(); w++){
					for(int t = 0; t < red.getT(); t++){
						this.enlaces.add(new LightLink(f, b, w, t, this));
					}
				}
			}
		}
	}

	/**
	 * Inicializa los valores del canal, en caso de que algun enlace este
	 * blocked, reservado.
	 */
	public void inicializar() {
		this.desbloquear();
		this.fibersExtra = 0;
		this.bandsExtra = 0;
		this.wavelengthsExtra = 0;
		
		this.enlacesNecesarios.clear();
		this.enlacesNecesarios.addAll(enlaces);
		
		for (LightLink e : enlaces) e.inicializar();
	}
	
	public Red getRed() {
		return red;
	}

	public void setRed(Red red) {
		this.red = red;
	}
	
	/**
	 * Getter del nodo Destino
	 * 
	 * @return Nodo Destino
	 */
	public Nodo getDestination() {
		return destination;
	}

	/* ************************
	 * Metodos de simulacion 
	 * *************************
	 */

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
	
	public boolean contains(LightLink e){
		return enlacesNecesarios.contains(e);
	}
	
	public void addExtraFiber(Red red){
		this.fibersExtra++;
		
		int newFiberId = red.getF() + fibersExtra - 1;
		
		for ( int b = 0; b < red.getB() + bandsExtra; b++ ){
			for ( int w = 0; w < red.getW() + wavelengthsExtra; w++ ){
				for ( int t = 0; t < red.getT(); t++ ){
					enlacesNecesarios.add( new LightLink(newFiberId, b, w, t, this) );
					freeBandwidth++;
				}
			}
		}
	}
	
	public void addExtraBand(Red red){
		this.bandsExtra++;
		
		int newBandId = red.getB() + bandsExtra - 1;
		
		for ( int f = 0; f < red.getF() + fibersExtra; f++ ){
			for ( int w = 0; w < red.getW() + wavelengthsExtra; w++ ){
				for ( int t = 0; t < red.getT(); t++ ){
					enlacesNecesarios.add( new LightLink(f, newBandId, w, t, this) );
					freeBandwidth++;
				}
			}
		}
	}
	
	public void addExtraWavelength(Red red){
		this.wavelengthsExtra++;
		
		int newWavelengthId = red.getW() + wavelengthsExtra - 1;
		
		for ( int f = 0; f < red.getF() + fibersExtra; f++ ){
			for ( int b = 0; b < red.getB() + bandsExtra; b++ ){
				for ( int t = 0; t < red.getT(); t++ ){
					enlacesNecesarios.add( new LightLink(f, b, newWavelengthId, t, this) );
					freeBandwidth++;
				}
			}
		}
	}
	
	public int getFibersExtra(){
		return this.fibersExtra;
	}
	
	public int getBandsExtra(){
		return this.bandsExtra;
	}

	public int getWavelengthsExtra(){
		return this.wavelengthsExtra;
	}

	public void setDestino(Nodo b) {
		this.destination = b;
	}

	public Nodo getSource() {
		return this.source;
	}

	public void setSource(Nodo a) {
		this.source = a;
	}
		
	public LightLink getLightLinkLibre(){
		if(blocked) return null;
		
		LightLink [] disponibles = new LightLink[enlacesNecesarios.size()];
		int i = 0;
		for(LightLink e: this.enlacesNecesarios){
			if(!e.isBlocked() && e.getTimeslot() == 0){
				disponibles[i++] = e;
			}
		}
		
		if ( i == 0 ){
			for(LightLink e: this.enlacesNecesarios){
				if(! e.isBlocked() ){
					disponibles[i++] = e;
				}
			}			
		}
		
		int sorteado = (int)(Math.random()*((double)i));
		
		return disponibles[sorteado];
	}
	
	private class PreferenceComparator implements Comparator<LightLink>{
		
		private LightLink base;
		
		@Override
		public int compare(LightLink arg0, LightLink arg1) {
			
			return base.distance(arg0) - base.distance(arg1);
		}
		
		
	}
	
	public LightLink getLightLinkLibre(LightLink base){
		PreferenceComparator pc = new PreferenceComparator();
		pc.base = base;
		TreeSet<LightLink> candidatos = new TreeSet<LightLink>(pc);
		
		for(LightLink e : enlacesNecesarios){
			
			if ( base.distance(e) == 0 ) {
				if (! e.isBlocked() )return e;
			}
			
			if ( e.getLightPathID() >= 0 || e.isBlocked() ) continue;
			
			candidatos.add(e);
		}
		
		if ( candidatos.isEmpty() ) System.err.println("Buscando a " + base);
		
		return candidatos.isEmpty() ? null : candidatos.first();
	}
	
	
	/**
	 * Bloquear el enlace porque forma parte del camino primario de algun
	 * Servicio
	 */
	public void bloquear() {
		this.blocked = true;
	}

	public void desbloquear() {
		this.blocked = false;
	}

	/**
	 * Retorna true si el enlace esta blocked.
	 * 
	 * @return
	 */
	public boolean isBlocked() {
		return this.blocked;
	}

	public int getCost() {
		return cost;
	}

	public void setCost(int cost) {
		this.cost = cost;
	}

	public Set<LightLink> getEnlaces() {
		return enlaces;
	}
	
	public LightLink updateReference(LightLink base){
		for( LightLink e: enlacesNecesarios){
			if( e.equals(base) ) return e;
		}
		
		return null;
	}

	public void setEnlaces(Set<LightLink> enlaces) {
		this.enlaces = enlaces;
		this.enlacesNecesarios.clear();
		this.enlacesNecesarios.addAll(enlaces);
		
		System.out.println("enlacesNecesarios : " + enlacesNecesarios.size());
	}

	@Override
	public String toString() {
		return source + "-" + destination;
	}
	
	public int getUso(){
		double total = 0;
		double utilizados = 0;
		for(LightLink e : enlacesNecesarios){
			total+=1;
			if(e.isBlocked()) utilizados+=1;
		}
		
		return (int) (100.0*utilizados/total);
	}

	@Override
	public int compareTo(Link arg0) {
		int cmpOrigen = source.compareTo(arg0.source);
		
		if ( cmpOrigen != 0 ) return cmpOrigen;
		
		return destination.compareTo(arg0.destination);
	}

	public boolean poseeCapacidad(int anchoDeBanda){
        int k = 1;
        for( LightLink e: enlacesNecesarios){
                if ( ! ( e.isBlocked() || e.getLightPathID() >= 0) ) {
                        if ( k == anchoDeBanda ) {
                        	return true;
                        } else {
                        	k++;
                        }
                }
        }
        
        //if ( freeBandwidth >= anchoDeBanda) System.out.println("free: " + freeBandwidth + " / k = " + k);
        
        return false;
	}
	
	public int getF(){
		return red.getB();
	}

	public int getB(){
		return red.getB();
	}
	
	public int getW(){
		return red.getW();
	}
	
	public int getT(){
		return red.getT();
	}
	
	public int getFreeBandwidth(){
		return this.freeBandwidth;
	}
	
	public void reduceBandwidth(){
		this.freeBandwidth--;
	}
	
	public void increaseBandwidth(){
		this.freeBandwidth++;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof Link)) return false;
		
		Link b = (Link) obj;
		
		return this.source.equals(b.source) && this.destination.equals(b.destination);
	}
}
